package at.ac.tuwien.infosys.jaxb;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.util.HashMap;
import java.util.Map;
import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Digits;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.Facets;
import javax.xml.bind.annotation.MinOccurs;
import com.sun.xml.bind.v2.model.annotation.AnnotationSource;
/**
* Processes JAXB-Facets annotation objects and provides default values from
* J2EE validation constraints. Values provided in the Facets annotations take
* precedence.
*
* <p>
* No validation is done between constraints and explicitly specified facets.
* Inconsistent validation constraints and Facets annotations will result in an
* inconsistent schema.
*
* <p>
* Supported facets: <br>
* <b>enumeration</b> by {@link javax.validation.constraints.AssertTrue},
* {@link javax.validation.constraints.AssertFalse} <br>
* <b>fractionDigits</b> by {@link javax.validation.constraints.Digits#fraction() } <br>
* <b>length</b> by {@link javax.validation.constraints.Size#min() },
* {@link javax.validation.constraints.Size#max() } (if identical)<br>
* <b>fractionDigits</b> by {@link javax.validation.constraints.Digits#fraction() } <br>
* <b>maxLength</b> by {@link javax.validation.constraints.Size#max() } <br>
* <b>minLength</b> by {@link javax.validation.constraints.Size#min() } <br>
* <b>maxInclusive</b> by {@link javax.validation.constraints.DecimalMax },
* {@link javax.validation.constraints.Max } (precedence order) <br>
* <b>minInclusive</b> by {@link javax.validation.constraints.DecimalMin },
* {@link javax.validation.constraints.Min } (precedence order) <br>
* <b>pattern</b> by {@link javax.validation.constraints.Pattern } <br>
* <b>totalDigits</b> by {@link javax.validation.constraints.Digits#fraction() } +
* {@link javax.validation.constraints.Digits#integer() } <br>
*
* <p>
* Other properties: <br>
* <b>minOccurs</b> property will be set to 1 if
* {@link javax.validation.constraints.NotNull } is present.<br>
*
* @author Varga Bence (vbence@czentral.org)
* @author Waldemar Hummer (whummer@hummer.io)
* @since JAXB-Facets 1.3.0
*/
public class ValidationFacetsFilter {
/**
* Amends an annotation with defaults taken from the AnnotatedElement given.
* given.
* @param cls Expected Annotation class.
* @param original Original annotation to amend (null is a valid value).
* @param elem Element to check for validation constraints to be used as
* defaults.
* @return Processed annotation object or null.
*/
public Annotation filterAnnotation(Class<? extends Annotation> cls, Annotation original, AnnotatedElement elem) {
return filterAnnotation(cls, original, new AnnotatedElementWrapper(elem));
}
/**
* Amends an annotation with defaults taken from the AnnotationSource given.
* @param cls Expected Annotation class.
* @param original Original annotation to amend (null is a valid value).
* @param info Info object to check for validation constraints to be used
* as defaults.
* @return Processed Annotation or null if no information available.
*/
public Annotation filterAnnotation(Class<? extends Annotation> cls, Annotation original, AnnotationSource info) {
if (cls == Facets.class) {
return filterFacets((Facets)original, info);
} else if (cls == MinOccurs.class) {
return filterMinOccurs((MinOccurs)original, info);
} else {
return original;
}
}
/**
* Process a Facets annotation.
* @param original Existing Facets annotation or null.
* @param info Source for validation constraints.
* @return Processed Annotation or null if no information available.
*/
private Facets filterFacets(Facets original, AnnotationSource info) {
Map<String, Object> annoValues = new HashMap<String, Object>(
AnnotationUtils.getAnnotationValues(Facets.class, original));
boolean override = false;
String[] enums = new String[0];
try {
enums = original.enumeration();
} catch (Exception e) {
/* swallow */
}
if (enums.length <= 0) {
if (info.readAnnotation(AssertFalse.class) != null) {
override = true;
annoValues.put("enumeration", new String[] { "false", "0" });
} else if (info.readAnnotation(AssertTrue.class) != null) {
override = true;
annoValues.put("enumeration", new String[] { "true", "1" });
}
}
long fractions = Facets.VOID_LONG;
try {
fractions = original.fractionDigits();
} catch (Exception e) {
/* swallow */
}
if (fractions == Facets.VOID_LONG) {
if (info.readAnnotation(Digits.class) != null) {
override = true;
annoValues.put("fractionDigits", (Long)(long)info.readAnnotation(Digits.class).fraction());
}
}
long length = Facets.VOID_LONG;
try {
length = original.length();
} catch (Exception e) {
/* swallow */
}
if (length == Facets.VOID_LONG) {
if (info.readAnnotation(Size.class) != null) {
Size size = info.readAnnotation(Size.class);
if (size.max() == size.min()) {
override = true;
annoValues.put("length", (Long)(long)size.max());
}
}
}
long maxLength = Facets.VOID_LONG;
try {
maxLength = original.maxLength();
} catch (Exception e) {
/* swallow */
}
if (maxLength == Facets.VOID_LONG) {
if (info.readAnnotation(Size.class) != null) {
override = true;
annoValues.put("maxLength", (Long)(long)info.readAnnotation(Size.class).max());
}
}
long minLength = Facets.VOID_LONG;
try {
minLength = original.minLength();
} catch (Exception e) {
/* swallow */
}
if (minLength == Facets.VOID_LONG) {
if (info.readAnnotation(Size.class) != null) {
override = true;
annoValues.put("minLength", (Long)(long)info.readAnnotation(Size.class).min());
}
}
String maxInclusive = Facets.VOID_STRING;
try {
maxInclusive = original.maxInclusive();
} catch (Exception e) {
/* swallow */
}
if (Facets.VOID_STRING.equals(maxInclusive)) {
if (info.readAnnotation(DecimalMax.class) != null) {
override = true;
annoValues.put("maxInclusive", info.readAnnotation(DecimalMax.class).value());
} else if (info.readAnnotation(Max.class) != null) {
override = true;
annoValues.put("maxInclusive", Long.toString(info.readAnnotation(Max.class).value()));
}
}
String minInclusive = Facets.VOID_STRING;
try {
minInclusive = original.minInclusive();
} catch (Exception e) {
/* swallow */
}
if (Facets.VOID_STRING.equals(minInclusive)) {
if (info.readAnnotation(DecimalMin.class) != null) {
override = true;
annoValues.put("minInclusive", info.readAnnotation(DecimalMin.class).value());
} else if (info.readAnnotation(Min.class) != null) {
override = true;
annoValues.put("minInclusive", Long.toString(info.readAnnotation(Min.class).value()));
}
}
String pattern = Facets.VOID_STRING;
try {
pattern = original.pattern();
} catch (Exception e) {
/* swallow */
}
if (Facets.VOID_STRING.equals(pattern)) {
if (info.readAnnotation(Pattern.class) != null) {
override = true;
annoValues.put("pattern", info.readAnnotation(Pattern.class).regexp());
}
}
long totalDigits = Facets.VOID_LONG;
try {
totalDigits = original.totalDigits();
} catch (Exception e) {
/* swallow */
}
if (totalDigits == Facets.VOID_LONG) {
if (info.readAnnotation(Digits.class) != null) {
override = true;
annoValues.put("totalDigits", (Long)(long)(info.readAnnotation(Digits.class).integer() +
info.readAnnotation(Digits.class).fraction()));
}
}
return override ? AnnotationUtils.createAnnotationProxy(Facets.class, annoValues) : original;
}
/**
* Process a MinOccurs annotation.
* @param original Existing MinOccurs annotation or null.
* @param info Source for validation constraints.
* @return Processed Annotation or null if no information available.
*/
private MinOccurs filterMinOccurs(MinOccurs original, AnnotationSource info) {
Map<String, Object> annoValues = new HashMap<String, Object>(
AnnotationUtils.getAnnotationValues(MinOccurs.class, original));
if (original == null && info.readAnnotation(NotNull.class) != null) {
annoValues.put("value", 1L);
return AnnotationUtils.createAnnotationProxy(MinOccurs.class, annoValues);
} else {
return original;
}
}
/**
* AnnotationSource implementation based on information from java reflection
* (AnnotatedElement).
*/
static class AnnotatedElementWrapper implements AnnotationSource {
private AnnotatedElement element;
public AnnotatedElementWrapper(AnnotatedElement element) {
this.element = element;
}
@Override
public <A extends Annotation> A readAnnotation(Class<A> type) {
return element.getAnnotation(type);
}
@Override
public boolean hasAnnotation(Class<? extends Annotation> type) {
return element.isAnnotationPresent(type);
}
}
}